﻿using S7.Net;
using SysLog;

namespace PLCComponent;

using S7.Net;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;

public class PlcConnectionPool : IDisposable
{
    // PLC连接配置类
    public class PlcConfig
    {
        public string? Name { get; set; }
        public string? IpAddress { get; set; }
        public short Rack { get; set; } = 0;
        public short Slot { get; set; } = 0;
        public CpuType CpuType { get; set; } = CpuType.S71200;
        public int HeartbeatInterval { get; set; } = 5000;
        public int ReconnectInterval { get; set; } = 3000;
        public int MaxRetryCount { get; set; } = 5;
        public int Timeout { get; set; } = 5000;
    }

    // PLC连接包装器
    private class PlcConnectionWrapper
    {
        public Plc Plc { get; }
        public PlcConfig Config { get; }
        public DateTime LastUsed { get; set; }
        public bool IsConnected => Plc?.IsConnected == true;
        public int RetryCount { get; set; }

        public PlcConnectionWrapper(PlcConfig config)
        {
            Config = config;
            Plc = new Plc(config.CpuType, config.IpAddress, config.Rack, config.Slot);
        }

        public void Connect()
        {
            Plc.Open();
            LastUsed = DateTime.Now;
            RetryCount = 0;
        }

        public void Disconnect()
        {
            Plc.Close();
        }
    }

    // 连接池核心数据结构
    private readonly ConcurrentDictionary<string, PlcConnectionWrapper> _connectionPool = new();
    private Timer _heartbeatTimer;
    private bool _disposed;

    public void StartHeartBeat()
    {
        // 心跳检测定时器 (每5秒检查一次)
        _heartbeatTimer = new Timer(CheckConnections, null, 5000, 5000);
    }

    // 添加PLC配置到连接池
    public void AddPlcConfig(PlcConfig config)
    {
        if (_connectionPool.ContainsKey(config.Name))
            throw new ArgumentException($"PLC '{config.Name}' already exists in pool");

        var wrapper = new PlcConnectionWrapper(config);
        _connectionPool[config.Name] = wrapper;
    }

    // 获取PLC连接
    private PlcConnectionWrapper GetConnection(string plcName)
    {
        if (!_connectionPool.TryGetValue(plcName, out var wrapper))
            throw new KeyNotFoundException($"PLC '{plcName}' not found in pool");
        // 连接管理策略
        try
        {
            if (!wrapper.IsConnected)
            {
                // TryReconnect(wrapper);
                wrapper.Connect();
                if (!wrapper.IsConnected)
                    throw new Exception($"Failed to connect to PLC '{plcName}'");
            }
        }
        catch (Exception e)
        {
            Console.WriteLine(e);
            throw;
        }
        wrapper.LastUsed = DateTime.Now;
        return wrapper;
    }

    // 心跳检测
    private void CheckConnections(object state)
    {
        foreach (var (_, wrapper) in _connectionPool)
        {
            try
            {
                // 定期检测连接状态
                if (!wrapper.IsConnected ||
                    (DateTime.Now - wrapper.LastUsed).TotalMilliseconds > wrapper.Config.HeartbeatInterval)
                {
                    // 使用异步防止阻塞主线程
                    Task.Run(() => VerifyConnection(wrapper));
                }
            }
            catch (Exception ex)
            {
                /* 记录日志 */
                if (ex.StackTrace != null) ILog.writeError(ex.StackTrace);
            }
        }
    }

    // 连接验证和重连
    private void VerifyConnection(PlcConnectionWrapper wrapper)
    {
        try
        {
            if (!wrapper.Plc.IsConnected)
            {
                TryReconnect(wrapper);
            }
        }
        catch (Exception ex)
        {
            /* 记录日志 */
            if (ex.StackTrace != null) ILog.writeError(ex.StackTrace);
        }
    }

    // 重连逻辑
    private void TryReconnect(PlcConnectionWrapper wrapper)
    {
        if (wrapper.RetryCount >= wrapper.Config.MaxRetryCount) return;

        try
        {
            wrapper.Disconnect();
            Thread.Sleep(wrapper.Config.ReconnectInterval);
            wrapper.Connect();
        }
        catch
        {
            wrapper.RetryCount++;
            // 指数退避策略
            var delay = (int)Math.Pow(2, wrapper.RetryCount) * 1000;
            Thread.Sleep(Math.Min(delay, 30000));
        }
    }

    // 断开指定PLC连接
    public void Disconnect(string plcName)
    {
        if (_connectionPool.TryRemove(plcName, out var wrapper))
        {
            wrapper.Disconnect();
        }
    }

    // 资源清理
    public void Dispose()
    {
        if (_disposed) return;

        _heartbeatTimer?.Dispose();

        foreach (var (_, wrapper) in _connectionPool)
        {
            try
            {
                wrapper.Disconnect();
                ILog.writeInfo($"plc disconnected:{wrapper.Config.Name}...");
            }
            catch
            {
                /* 忽略关闭异常 */
            }
        }

        _connectionPool.Clear();

        _disposed = true;
        GC.SuppressFinalize(this);
    }


    // 读取DB块数据  "DB1.DBD0"
    // 读取任意类型数据
    public object? Read(string plcName, string variable)
    {
        try
        {
            var connection = GetConnection(plcName);
            return connection.Plc.Read(variable);;
        }
        catch (Exception e)
        {
            ILog.writeError($"Plc '{plcName}' read data error: {e.Message}");
        }
        return null;
    }

    // 写入任意类型数据
    public void Write(string plcName, string variable, object value)
    {
        try
        {
            var connection = GetConnection(plcName);
            connection.Plc.Write(variable, value);
        }
        catch (Exception e)
        {
            ILog.writeError($"Plc '{plcName}' write data error: {e.Message}");
        }
    }

    // =============== 类型化读取方法 ===============

    // 读取位 (Bit) - 地址格式示例: "DB1.DBX0.0"
    public bool ReadBit(string plcName, string bitAddress)
    {
        return (bool)Read(plcName, bitAddress);
    }

    // 读取字节 (Byte) - 地址格式示例: "DB1.DBB0"
    public byte ReadByte(string plcName, string byteAddress)
    {
        return (byte)Read(plcName, byteAddress);
    }

    // 读取字 (Word) - 地址格式示例: "DB1.DBW0"
    public ushort ReadWord(string plcName, string wordAddress)
    {
        return (ushort)Read(plcName, wordAddress);
    }

    // 读取双字 (DWord) - 地址格式示例: "DB1.DBD0"
    public uint ReadDWord(string plcName, string dwordAddress)
    {
        return (uint)Read(plcName, dwordAddress);
    }

    // 读取整数 (Int) - 地址格式示例: "DB1.DBW0(Int)"
    public short ReadInt(string plcName, string intAddress)
    {
        ushort result = (ushort)Read(plcName, intAddress);
        return (short)result;
    }

    // 读取双整数 (DInt) - 地址格式示例: "DB1.DBD0(DInt)"
    public int ReadDInt(string plcName, string dintAddress)
    {
        return (int)Read(plcName, dintAddress);
    }

    // 读取实数 (Real) - 地址格式示例: "DB1.DBD0(Real)"
    public float ReadReal(string plcName, string realAddress)
    {
        return (float)Read(plcName, realAddress);
    }

    // 读取长实数 (LReal) - 地址格式示例: "DB1.DBD0(LReal)"
    public double ReadLReal(string plcName, string lrealAddress)
    {
        return (double)Read(plcName, lrealAddress);
    }

    // 读取字符串 (String) - 地址格式示例: "DB1.DBB0(String,20)"
    public string ReadString(string plcName, string stringAddress)
    {
        return (string)Read(plcName, stringAddress);
    }

    // 读取日期时间 (DateTimeLong) - 地址格式示例: "DB1.DBD0(DateTimeLong)"
    public DateTime ReadDateTimeLong(string plcName, string datetimeAddress)
    {
        return (DateTime)Read(plcName, datetimeAddress);
    }

    // 读取宽字符串 (S7WString) - 地址格式示例: "DB1.DBB0(WString,20)"
    public string ReadWString(string plcName, string wstringAddress)
    {
        return (string)Read(plcName, wstringAddress);
    }

    // =============== 类型化写入方法 ===============

    // 写入位 (Bit) - 地址格式示例: "DB1.DBX0.0"
    public void WriteBit(string plcName, string bitAddress, bool value)
    {
        Write(plcName, bitAddress, value);
    }

    // 写入字节 (Byte) - 地址格式示例: "DB1.DBB0"
    public void WriteByte(string plcName, string byteAddress, byte value)
    {
        Write(plcName, byteAddress, value);
    }

    // 写入字 (Word) - 地址格式示例: "DB1.DBW0"
    public void WriteWord(string plcName, string wordAddress, ushort value)
    {
        Write(plcName, wordAddress, value);
    }

    // 写入双字 (DWord) - 地址格式示例: "DB1.DBD0"
    public void WriteDWord(string plcName, string dwordAddress, uint value)
    {
        Write(plcName, dwordAddress, value);
    }

    // 写入整数 (Int) - 地址格式示例: "DB1.DBW0(Int)"
    public void WriteInt(string plcName, string intAddress, short value)
    {
        Write(plcName, intAddress, value);
    }

    // 写入双整数 (DInt) - 地址格式示例: "DB1.DBD0(DInt)"
    public void WriteDInt(string plcName, string dintAddress, int value)
    {
        Write(plcName, dintAddress, value);
    }

    // 写入实数 (Real) - 地址格式示例: "DB1.DBD0(Real)"
    public void WriteReal(string plcName, string realAddress, float value)
    {
        Write(plcName, realAddress, value);
    }

    // 写入长实数 (LReal) - 地址格式示例: "DB1.DBD0(LReal)"
    public void WriteLReal(string plcName, string lrealAddress, double value)
    {
        Write(plcName, lrealAddress, value);
    }

    // 写入字符串 (String) - 地址格式示例: "DB1.DBB0(String,20)"
    public void WriteString(string plcName, string stringAddress, string value)
    {
        Write(plcName, stringAddress, value);
    }

    // 写入日期时间 (DateTimeLong) - 地址格式示例: "DB1.DBD0(DateTimeLong)"
    public void WriteDateTimeLong(string plcName, string datetimeAddress, DateTime value)
    {
        Write(plcName, datetimeAddress, value);
    }

    // 写入宽字符串 (S7WString) - 地址格式示例: "DB1.DBB0(WString,20)"
    public void WriteWString(string plcName, string wstringAddress, string value)
    {
        Write(plcName, wstringAddress, value);
    }
}